/*
   Copyright (C) 2006
   Andrew Madigan

This file is part of nhttpd.

nhttpd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

nhttpd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with nhttpd; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version.
*/

#include <ltdl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <pthread.h>
#include "nhexec.h"
#include <nhttpd/string.h>
#include <nhttpd/srvsocket.h>
#include <nhttpd/log.h>
#include "servcontext.h"
#include "uidgid.h"

void NH_init() {
	lt_dlinit();
	detect_init();
}

void NH_exit() {
	lt_dlexit();
}

void NP_Free(void* _self) {
  NH_Process* self = _self;

  if (self->socket != -1) shutdown(self->socket, SHUT_RDWR);

  O_Free(self->applications);
  O_Free(self->connectors);
  O_Free(self->mappings);

  free(self);
}

Class NH_ProcessClass = {NP_Free};

NH_Process* NP_CreateUG(uidgid ug) {
	NH_Process* self = malloc(sizeof(NH_Process));
  self->super.methods = &NH_ProcessClass;

  self->child = -1;
  self->socket = -1;
  self->applications = L_Create();
  self->connectors = L_Create();
  self->mappings = L_Create();
  self->children = NULL;
  self->uid = ug.uid;
  self->gid = ug.gid;
  return self;
}
	

NH_Process* NP_Create(Configuration* config) {
  NH_Process* self = malloc(sizeof(NH_Process));
  self->super.methods = &NH_ProcessClass;

  self->child = -1;
  self->socket = -1;
  self->applications = L_Create();
  self->connectors = L_Create();
  self->mappings = L_Create();
  self->children = L_Create();
  
  
  self->uid = config->uid;
  self->gid = config->gid;
  
  Hashtable* children = HT_Create(true, false);
  HT_Put(children, (char*) UG_Create(self->uid, self->gid), self);
  
  ListIter* iter = LI_Create(config->servers);
  ListIter* iter2;
  bool serverSame, first;
  Server* current;
  uidgid prev;
  uidgid cuid;
  Application* app;
  NH_Process* proc = NULL;
  while (LI_Next(iter)) {
	  serverSame = true;
	  first = true;
	  current = LI_GetValue(iter);
	  current->context = SC_Create(current);
	  iter2 = LI_Create(current->applications);
	  
	  while (LI_Next(iter2)) {
		  app = LI_GetValue(iter2);
		  if (first) {
			  prev.uid = app->uid;
			  prev.gid = app->gid;
		  }
		  else serverSame = app->uid == prev.uid && app->gid == prev.gid;
		  cuid.uid = app->uid;
		  cuid.gid = app->gid;
		  proc = HT_Get(children, (char*) &cuid);
		  if (proc == NULL) {
			  proc = NP_CreateUG(cuid);
			  HT_Put(children, (char*) UG_Create(app->uid, app->gid), proc);
			  L_Add(self->children, proc);
		  }
		  L_Add(proc->applications, app);
		  app->host = proc;
	  }
	  
	  O_Free(iter2);
	  
	  
	  if (serverSame) {
		  //If all of the applications in this server are running
		  //under the same process, that process takes on responsibility
		  //for the server's connectors and mappings
		  
		  iter2 = LI_Create(current->connectors);
		  while (LI_Next(iter2)) {
			  L_Add(proc->connectors, LI_GetValue(iter2));
		  }
		  O_Free(iter2);
		  iter2 = LI_Create(current->mappings);
		  while (LI_Next(iter2)) {
			  L_Add(proc->mappings, current->mappings);
		  }
		  O_Free(iter2);
	  }
  }
  O_Free(iter);
  O_Free(children);
  

  return self;
}

bool NP_Fork(NH_Process* self) {
  int sockets[2];
  socketpair(PF_UNIX, SOCK_DGRAM, 0, sockets);
  
  int pid = fork();
  if (pid == 0) {
	 self->socket = sockets[0];
	 self->child = 0;
	 return true;
  }
  else if (pid == -1) {
	 return false;
  }
  else {
	 self->socket = sockets[1];
	 self->child = 1;
	 NP_Run(self);
	 exit(0);
	 return true;
  }
}

typedef struct {
	HTTP_Request* request;
	Application* app;
} RequestData;

/*
This is the function run in a thread pool that passes data from the parent
process to child processes.

NOTE: This may be a bit non-portable
*/
void NP_ParentRouteThread(void* _self, void* _data) {
	NH_Process* self = _self;
	RequestData* data = _data;
	
	String* reqflattened = HRQ_Flatten(data->request);
	
	struct msghdr hdr;
	struct cmsghdr chdr[2];
	struct iovec vec[2];
	
	memset(&hdr, 0, sizeof(struct msghdr));
	vec[1].iov_base = reqflattened->buf;
	vec[1].iov_len = reqflattened->length;
	vec[0].iov_base = &(data->app);
	vec[0].iov_len = sizeof(Application*);
	hdr.msg_iov = (struct iovec*) &vec;
	hdr.msg_iovlen = 2;
	hdr.msg_control = (caddr_t)&chdr[0];
	hdr.msg_controllen = sizeof(chdr[0]) + sizeof(int);
	
	chdr[0].cmsg_level = SOL_SOCKET;
	chdr[0].cmsg_type = SCM_RIGHTS;
	chdr[0].cmsg_len = sizeof(chdr[0]) + sizeof(int);
	*(int *)&chdr[1] = SO_GetFD(data->request->socket);
	
	if ( sendmsg(self->socket,&hdr,0) < 0 ) {
		NH_log(LOG_CRIT, "Unable to send request across socket: %m");
	}
	
	O_Free(data->request);
	free(data);
	O_Free(reqflattened);
}

//This is where all requests eventually end up
void NP_ExecAppThread(void* _self, void* _data) {
	RequestData* data = _data;
	
	//We assume the servlet has been initialized
	
	HRQ_ReadExtra(data->request);
	HTTP_Response* response = HRS_Create(data->request);
	data->app->servlet->Service(data->app->servlet, data->request, response);
	
	Socket* sock = data->request->socket;
	O_Free(data->request);
	SO_SetReader(sock, NULL);
	
	//In the future, we will probably need to do the same thing to the Writer
	//otherwise the socket free function will free the writer, which will be
	//freed again when the response frees its writer.
	O_Free(sock);
	O_Free(response);
	free(data);
}

typedef struct {
	char* flattened;
	int fd;
	Application* app;
} RequestMessage;

/*
This runs in the child thread pool to handle unflattening the message before
passing it on to NP_ExecAppThread.
*/
void NP_DecodeRequestThread(void* _self, void* _data) {
	NH_Process* self = _self;
	RequestMessage* data = _data;
	
	RequestData* req = malloc(sizeof(req));
	req->request = HRQ_Unflatten(data->flattened, data->fd);
	free(data->flattened);
	req->app = data->app;
	
	NP_ExecAppThread(self, req);
	free(data);
}



void NP_ChildMain(NH_Process* self) {
	RequestMessage* msg;
	int length;
	struct msghdr hdr;
	struct cmsghdr chdr[2];
	struct iovec vec[2];
	
	memset(&hdr, 0, sizeof(struct msghdr));
	vec[0].iov_len = sizeof(Application*);
	hdr.msg_iov = (struct iovec*) &vec;
	hdr.msg_iovlen = 2;
	hdr.msg_control = (caddr_t)&chdr[0];
	hdr.msg_controllen = sizeof(chdr[0]) + sizeof(int);
	
	while (true) {
		msg = malloc(sizeof(RequestMessage));
		vec[0].iov_base = &(msg->app);
		vec[1].iov_len = 0;
		length = recvmsg(self->socket, &hdr, MSG_PEEK);
		vec[1].iov_base = msg->flattened = malloc(length - sizeof(int));
		vec[1].iov_len = length - sizeof(int);
		recvmsg(self->socket, &hdr, 0);
		msg->fd = *(int*) &chdr[1];
		TP_Process(self->pool, msg);
	}
}

/*
-Get a socket
-Initialize request
-Run through mappings associated with the server that is this connectors parent
-Get the parent of the application we map to and run it
*/
void* NP_ConnectorThread(void* _data) {
	Connector* conn = _data;
	
	Socket* sock;
	int match;
	ListIter* iter;
	HTTP_Request* request;
	Application* app;
	
	while (true) {
		app = NULL;
		sock = SRV_Accept(conn->sock);
		if (sock == NULL) continue;
		request = HRQ_Create(sock);
		iter = LI_Create(conn->parent->mappings);
		while (LI_Next(iter)) {
			match = NH_matchesMapping(request->uri,
				((Mapping*) LI_GetValue(iter)));
			if (match >= 0) {
				app = ((Mapping*) LI_GetValue(iter))->app;
				if (match == 1) break;
			}
		}
		O_Free(iter);
		if (app == NULL) {
			NH_log(LOG_CRIT, "Unable to find match for %s", request->uri);
			//Error handling here
			O_Free(sock);
			O_Free(request);
		}
		else {
			NP_Route((NH_Process*) app->host, request, app);
		}
	}
	return NULL;
}
	
void NP_InitConnectors(NH_Process* self) {
	ListIter* iter = LI_Create(self->connectors);
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_t thread;
	
	Connector* conn;
	
	while (LI_Next(iter)) {
		conn = LI_GetValue(iter);
		conn->sock = SRV_Create(conn->addr, conn->addrlen);
		if (pthread_create(&thread, &attr, NP_ConnectorThread, LI_GetValue(iter)))
		{
				NH_log(LOG_CRIT, "Error creating connector thread: %m");
		}
	}
	O_Free(iter);
	pthread_attr_destroy(&attr);
}

void NP_InitApplications(NH_Process* self) {
	ListIter* iter = LI_Create(self->applications);
	
	Application* app;
	
	while (LI_Next(iter)) {
		app = LI_GetValue(iter);
		SC_InitApplication(app->server->context, app);
	}
	O_Free(iter);
}
			

void NP_Main(NH_Process* self) {
	
	NP_InitConnectors(self);
	if (self->gid < 0) {
		if (setgid(self->gid) < 0) {
			NH_log(LOG_CRIT, "Unable to setgid: %m");
			return;
		}
	}
	if (self->uid < 0) {
		if (setuid(self->uid) < 0) {
			NH_log(LOG_CRIT, "Unable to setuid: %m");
			return;
		}
	}
	NP_InitApplications(self);
	TP_AssignSelf(self->pool);
}
		
void NP_Run(NH_Process* self) {
	if (self->children != NULL) {
		//Fork all child processes
		ListIter* children = LI_Create(self->children);
		while (LI_Next(children)) {
			NP_Fork((NH_Process*) LI_GetValue(children));
		}
	}
	if (self->child < 0) {
		self->pool = TP_Create(NP_ExecAppThread, self, 1);
		NP_Main(self);
	}
	else {
		if (self->gid < 0) {
			if (setgid(self->gid) < 0) {
				NH_log(LOG_CRIT, "Unable to setgid: %m");
				return;
			}
		}
		if (self->uid < 0) {
			if (setuid(self->uid) < 0) {
				NH_log(LOG_CRIT, "Unable to setuid: %m");
				return;
			}
		}
		if (self->child == 0) {
			self->pool = TP_Create(NP_ParentRouteThread, self, 1);
		}
		else {
			self->pool = TP_Create(NP_DecodeRequestThread, self, 1);
			NP_ChildMain(self);
		}	
	}
}


void NP_Route(NH_Process* self, HTTP_Request* request, Application* app) {
	RequestData* data = malloc(sizeof(RequestData));
	data->request = request;
	data->app = app;
	TP_Process(self->pool, data);
}
